/*******************************************************************************
 * Copyright (c) 2012 Lai Duy Ha & Ordina and committers to Mod4j
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Ordina - initial implementation
 *     Lai Duy Ha - port to new version of Xtext
 *******************************************************************************/
package org.mod4j.businessdomain.generator.helpers;

import java.util.ArrayList;
import java.util.List;

import com.ldh.mod4j.businessdomain.businessDomainDsl.AbstractBusinessRule;
import com.ldh.mod4j.businessdomain.businessDomainDsl.Association;
import com.ldh.mod4j.businessdomain.businessDomainDsl.BoolProperty;
import com.ldh.mod4j.businessdomain.businessDomainDsl.BusinessClass;
import com.ldh.mod4j.businessdomain.businessDomainDsl.BusinessDomainModel;
import com.ldh.mod4j.businessdomain.businessDomainDsl.DateTimeProperty;
import com.ldh.mod4j.businessdomain.businessDomainDsl.DecimalProperty;
import com.ldh.mod4j.businessdomain.businessDomainDsl.Enumeration;
import com.ldh.mod4j.businessdomain.businessDomainDsl.EnumerationProperty;
import com.ldh.mod4j.businessdomain.businessDomainDsl.IntegerProperty;
import com.ldh.mod4j.businessdomain.businessDomainDsl.Multiplicity;
import com.ldh.mod4j.businessdomain.businessDomainDsl.Property;
import com.ldh.mod4j.businessdomain.businessDomainDsl.StringProperty;
import com.ldh.mod4j.businessdomain.businessDomainDsl.UniqueRule;

public class BusinessClassHelpers {

	public static String getCollectionInterface(Association a) {
		if (isOne2Many(a) || isMany2Many(a)) {
			return a.isOrdered() ? "List" : "Set";
		}
		return null;
	}

	public static String getCollectionImplementation(Association a) {
		if (isOne2Many(a) || isMany2Many(a)) {
			return a.isOrdered() ? "ArrayList" : "HashSet";
		}
		return null;
	}

	public static boolean isOne2Many(Association a) {
		Multiplicity srcMult = a.getSourceMultiplicity();
		Multiplicity tgtMult = a.getTargetMultiplicity();
		return ((srcMult == Multiplicity.ONE) || (srcMult == Multiplicity.ZERO_ONE))
				&& ((tgtMult == Multiplicity.ONE_MANY) || (tgtMult == Multiplicity.ZERO_MANY));
	}

	public static boolean isOne2One(Association a) {
		Multiplicity srcMult = a.getSourceMultiplicity();
		Multiplicity tgtMult = a.getTargetMultiplicity();
		return ((srcMult == Multiplicity.ONE) || (srcMult == Multiplicity.ZERO_ONE))
				&& ((tgtMult == Multiplicity.ONE) || (tgtMult == Multiplicity.ZERO_ONE));
	}

	public static boolean isMany2One(Association a) {
		Multiplicity srcMult = a.getSourceMultiplicity();
		Multiplicity tgtMult = a.getTargetMultiplicity();
		return ((srcMult == Multiplicity.ONE_MANY) || (srcMult == Multiplicity.ZERO_MANY))
				&& ((tgtMult == Multiplicity.ONE) || (tgtMult == Multiplicity.ZERO_ONE));
	}

	public static boolean isMany2Many(Association a) {
		Multiplicity srcMult = a.getSourceMultiplicity();
		Multiplicity tgtMult = a.getTargetMultiplicity();
		return ((srcMult == Multiplicity.ONE_MANY) || (srcMult == Multiplicity.ZERO_MANY))
				&& ((tgtMult == Multiplicity.ONE_MANY) || (tgtMult == Multiplicity.ZERO_MANY));
	}

	// public static boolean isCompositePart(BusinessClass cls) {
	// for (Association a : cls.getAssociationsFrom()) {
	// if (a.isComposite() && (a.getSourceMultiplicity() == Multiplicity.ONE)) {
	// return true;
	// }
	// }
	// return false;
	// }

	public static String javaType(Property p) {
		if (p instanceof BoolProperty) {
			return javaType((BoolProperty) p);
		} else if (p instanceof StringProperty) {
			return javaType((StringProperty) p);
		} else if (p instanceof IntegerProperty) {
			return javaType((IntegerProperty) p);
		} else if (p instanceof DecimalProperty) {
			return javaType((DecimalProperty) p);
		} else if (p instanceof EnumerationProperty) {
			return javaType((EnumerationProperty) p);
		} else if (p instanceof DateTimeProperty) {
			return javaType((DateTimeProperty) p);
		}
		return "Object";
	}

	public static String javaNullableType(Property p) {
		if (p instanceof BoolProperty) {
			return javaNullableType((BoolProperty) p);
		} else if (p instanceof StringProperty) {
			return javaNullableType((StringProperty) p);
		} else if (p instanceof IntegerProperty) {
			return javaNullableType((IntegerProperty) p);
		} else if (p instanceof DecimalProperty) {
			return javaNullableType((DecimalProperty) p);
		} else if (p instanceof EnumerationProperty) {
			return javaNullableType((EnumerationProperty) p);
		} else if (p instanceof DateTimeProperty) {
			return javaNullableType((DateTimeProperty) p);
		}
		return "Object";
	}

	public static String javaDefaultValue(Property p) {
		if (p instanceof BoolProperty) {
			return javaDefaultValue((BoolProperty) p);
		} else if (p instanceof StringProperty) {
			return javaDefaultValue((StringProperty) p);
		} else if (p instanceof IntegerProperty) {
			return javaDefaultValue((IntegerProperty) p);
		} else if (p instanceof DecimalProperty) {
			return javaDefaultValue((DecimalProperty) p);
		} else if (p instanceof EnumerationProperty) {
			return javaDefaultValue((EnumerationProperty) p);
		} else if (p instanceof DateTimeProperty) {
			return javaDefaultValue((DateTimeProperty) p);
		}
		return "null";
	}

	public static String javaType(BoolProperty p) {
		return javaNullableType(p);
	}

	public static String javaNullableType(BoolProperty p) {
		return "Boolean";
	}

	public static String javaDefaultValue(BoolProperty p) {
		return "true";
	}

	public static String javaType(DateTimeProperty p) {
		return "DateTime";
	}

	public static String javaNullableType(DateTimeProperty p) {
		return javaType(p);
	}

	public static String javaDefaultValue(DateTimeProperty p) {
		return "null";
	}

	public static String javaType(EnumerationProperty p) {
		Enumeration e = p.getType();
		if (e == null) {
			System.err.println("ERROR in javaType() for EnumerationProperty");
			return "Object";
		}
		return p.getType().getName();
	}

	public static String javaNullableType(EnumerationProperty p) {
		return javaType(p);
	}

	public static String javaDefaultValue(EnumerationProperty p) {
		return "null";
	}

	public static String javaType(StringProperty p) {
		return "String";
	}

	public static String javaNullableType(StringProperty p) {
		return javaType(p);
	}

	public static String javaDefaultValue(StringProperty p) {
		return "null";
	}

	public static String javaType(IntegerProperty p) {
		return javaNullableType(p);
	}

	public static String javaNullableType(IntegerProperty p) {
		return "Integer";
	}

	public static String javaDefaultValue(IntegerProperty p) {
		return p.isNullable() ? "null" : "0";
	}

	public static String javaType(DecimalProperty p) {
		return javaNullableType(p);
	}

	public static String javaNullableType(DecimalProperty p) {
		return "BigDecimal";
	}

	public static String javaDefaultValue(DecimalProperty p) {
		return p.isNullable() ? "null" : "0F";
	}

	/**
	 * Get all unique rules of business class
	 * @param cls: Business class
	 * @return: List of unique rules
	 */
	public static List<UniqueRule> getUniqueRules(BusinessClass cls){
		List<UniqueRule> result = new ArrayList<UniqueRule>();
		for (AbstractBusinessRule businessRule : cls.getBusinessRules()) {
			if(businessRule instanceof UniqueRule){
				result.add((UniqueRule)businessRule);
			}
		}
		return result;
	}
	
	/**
	 * Get all properties not only from class but also from its ancestors
	 * 
	 * @param cls
	 *            : Business class
	 * @return: List of properties from class and its ancestors
	 */
	public static List<Property> getAllProperties(BusinessClass cls) {
		List<Property> result = new ArrayList<Property>();
		if (cls.getSuperclass() != null) {
			result.addAll(getAllProperties(cls.getSuperclass()));
		}
		result.addAll(cls.getProperties());
		return result;
	}

	/**
	 * Get all property on business class that is not derived
	 * 
	 * @param cls
	 *            : Business class
	 * @return List of not dervied properties form class
	 */
	public static List<Property> getNotDerviedProperties(BusinessClass cls) {
		List<Property> result = new ArrayList<Property>();
		for (Property property : cls.getProperties()) {
			if (!property.isDerived())
				result.add(property);
		}
		return result;
	}

	/**
	 * Get all association to not only for itself but also for its ancestor
	 * 
	 * @param cls
	 *            : Business class
	 * @return: Associations that have business class and its ancestor belong to
	 *          source size
	 */
	public static List<Association> getAllAssociationsTo(BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		if (cls.getSuperclass() != null) {
			result.addAll(getAllAssociationsTo(cls.getSuperclass()));
		}
		result.addAll(getAssociationsTo(cls));
		return result;
	}

	/**
	 * Get all association from
	 * 
	 * @param cls
	 *            : Business class
	 * @return: Associations that have business class belong to target size
	 */
	public static List<Association> getAllAssociationsFrom(BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		if (cls.getSuperclass() != null) {
			result.addAll(getAllAssociationsFrom(cls.getSuperclass()));
		}
		result.addAll(getAssociationsFrom(cls));
		return result;
	}

	/**
	 * Get all association from and its target multiplicity is "one"
	 * 
	 * @param cls: Business class
	 * @return Associations that have business class belong to target size and
	 *         its target multiplicity is "one"
	 */
	public static List<Association> getToOneAssociationsFrom(BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		for (Association association : getAssociationsFrom(cls)) {
			if ((association.getSourceMultiplicity().compareTo(
					Multiplicity.ZERO_ONE) == 0)
					&& association.isBidirectional()) {
				result.add(association);
			}
		}
		return result;
	}

	/**
	 * Get all associationto and its target multiplicity is "one"
	 * 
	 * @param cls
	 * @return Associations that have business class belong to source size and
	 *         its target multiplicity is "one"
	 */
	public static List<Association> getToOneAssociationsTo(BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		for (Association association : getAssociationsTo(cls)) {
			if (association.getTargetMultiplicity().compareTo(
					Multiplicity.ZERO_ONE) == 0) {
				result.add(association);
			}
		}
		return result;
	}

	/**
	 * Get all associationTo
	 * 
	 * @param cls
	 *            : Business class
	 * @return: Associations that have business class belong to source size
	 */
	public static List<Association> getAssociationsTo(BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		BusinessDomainModel model = BusinessDomainModelHelpers.getModel(cls);
		for (Association association : model.getAssociations()) {
			if (association.getSource().getName().equals(cls.getName())) {
				result.add(association);
			}
		}
		return result;
	}

	/**
	 * Get all Bidirectional association from
	 * 
	 * @param cls
	 *            : Business class
	 * @return: Bidirectional associations that have business class belong to
	 *          target size
	 */
	public static List<Association> getBidirectionalAssociationsFrom(
			BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		for (Association association : getAssociationsFrom(cls)) {
			if (association.isBidirectional()) {
				result.add(association);
			}
		}
		return result;
	}

	/**
	 * Get all AssociationFrom Note: This method not treat bidirectional
	 * associations
	 * 
	 * @param cls
	 *            : Business class
	 * @return: Associations that have business class belong to target size
	 */
	public static List<Association> getAssociationsFrom(BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		BusinessDomainModel model = BusinessDomainModelHelpers.getModel(cls);
		for (Association association : model.getAssociations()) {
			if (association.getTarget().getName().equals(cls.getName())) {
				result.add(association);
			}
		}
		return result;
	}

	/**
	 * Get all association related to business class (and its ancestor) and its
	 * target multiplicity is "one"
	 * 
	 * @param cls
	 * @return Associations that related to business class (and its ancestors)
	 *         and its target multiplicity is "one"
	 */
	public static List<Association> getAllToOneAssociationsInHierarchy(
			BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		result.addAll(getToOneAssociationsToInHierarchy(cls));
		result.addAll(getToOneAssociationsFromInHierarchy(cls));
		return result;
	}

	/**
	 * Get all AssociationTo and its target multiplicity is "one" not only for
	 * business class but also for its ancestors
	 * 
	 * @param cls
	 * @return Associations that have business class (and its ancestors) belong
	 *         to source size and its target multiplicity is "one"
	 */
	public static List<Association> getToOneAssociationsToInHierarchy(
			BusinessClass cls) {
		List<Association> result = new ArrayList<Association>();
		if (cls.getSuperclass() != null) {
			result.addAll(getToOneAssociationsToInHierarchy(cls.getSuperclass()));
		}
		result.addAll(getToOneAssociationsTo(cls));
		return result;
	}

	/**
	 * Get all AssociationFrom and its target multiplicity is "one" not only for
	 * business class but also for its ancestors
	 * 
	 * @param cls
	 * @return Associations that have business class (and its ancestors) belong
	 *         to target size and its target multiplicity is "one"
	 */
	public static List<Association> getToOneAssociationsFromInHierarchy(
			BusinessClass cls) {

		List<Association> result = new ArrayList<Association>();
		if (cls.getSuperclass() != null) {
			result.addAll(getToOneAssociationsFromInHierarchy(cls
					.getSuperclass()));
		}
		result.addAll(getToOneAssociationsFrom(cls));
		return result;
	}

	/**
	 * Check whether business class have sub class or not
	 * 
	 * @param clazz
	 * @return
	 */
	public static boolean hasSubclasses(BusinessClass clazz) {
		BusinessDomainModel model = (BusinessDomainModel) clazz.eContainer();
		for (BusinessClass type : model.getTypes()) {
			if (type instanceof BusinessClass) {
				BusinessClass businessClass = (BusinessClass) type;
				if (businessClass.getSuperclass() != null) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Check whether the ancestorCandidate is the ancestor of business class
	 * 
	 * @param clazz
	 *            : Business class
	 * @param ancestorCandidate
	 *            : ancestor candidate
	 * @return: true if ancestor candidate is the ancestor of clazz
	 */
	public static boolean isAncestorOf(BusinessClass clazz,
			BusinessClass ancestorCandidate) {
		for (BusinessClass ancestor : getAncestors(clazz)) {
			if (ancestorCandidate.getName().equals(ancestor.getName())) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Get list ancestors of business class
	 * 
	 * @param clazz
	 * @return: List<BusinessClass>
	 */
	public static List<BusinessClass> getAncestors(BusinessClass clazz) {
		List<BusinessClass> result = new ArrayList<BusinessClass>();
		if (clazz.getSuperclass() != null) {
			if (clazz.getSuperclass().getSuperclass() != null) {
				result.addAll(getAncestors(clazz.getSuperclass()));
			}
			result.add(clazz.getSuperclass());
		}
		return result;
	}
}
